Hooks自动化:让Claude Code在关键时刻自动执行你的规则
章节概述
前面我们学了MCP(加工具)、Skills(加知识)、Agent(加帮手)。本章介绍最后一种扩展方式——Hooks(钩子):在特定事件发生时,自动执行你预设的命令。
和前三者的区别,先给你一张表:
| MCP | Skills | Agent | Hooks | |
|---|---|---|---|---|
| 是什么 | 给Claude加工具 | 给Claude加知识/流程 | 给Claude加帮手 | 给Claude加"自动触发的规则" |
| 类比 | 给工人一把新扳手 | 给工人一本操作手册 | 雇一个新工人 | 在车间装自动感应门 |
| 谁在干活 | Claude自己用新工具 | Claude自己按手册 | 另一个Claude替你干活 | 没人干活——只是一段自动执行的命令 |
| 触发方式 | Claude决定什么时候用 | Claude决定什么时候用 | 你告诉它去干活 | 事件自动触发,不需要人参与 |
| 例子 | 安装Playwright操作浏览器 | /frontend-design 生成页面 | /commit 派Agent提交代码 | 每次Edit文件后自动跑lint |
| 配置位置 | settings.json | .md文件 | .md文件 | settings.json |
一句话总结:MCP/Skills/Agent都是被调用的,Hooks是自动触发的。你不用提醒Claude"帮我跑一下lint",它在每次编辑文件后会自己跑。
一、从一个例子开始:Edit文件后自动跑代码检查
1.1 问题场景
你用Claude Code写代码,让它改了一个Vue文件。改完之后你想跑一下ESLint确认没问题,但你每次都要手动说"跑一下lint"。能不能让它改完文件自动跑?
1.2 跟我做:配置你的第一个Hook
在项目的 .claude/settings.json 中添加:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/lint-on-edit.sh"
}
]
}
]
}
}
json
再写脚本 .claude/hooks/lint-on-edit.sh(记得 chmod +x):
#!/bin/bash
# Hook 的输入是 stdin 上的一段 JSON,用 jq 取出本次编辑的文件路径
FILE=$(jq -r '.tool_input.file_path')
if [ -f "$FILE" ]; then
npx eslint --fix "$FILE"
fi
exit 0
bash
现在每次Claude用Write或Edit修改文件后,会自动执行 eslint --fix,全程不消耗token——Hook 是本地命令,不经过模型。
注意这个细节:Hook 的上下文(工具名、参数、文件路径)是通过 stdin 传给脚本的 JSON,不是环境变量。官方示例统一用 jq -r '.tool_input.file_path' 这种模式读取,照这个写法来就不会踩坑。
1.3 更省事的配法:让 AI 帮你配
Hooks 的 JSON 结构不用背。直接在 Claude Code 里用自然语言描述:
帮我配置一个钩子,在本项目中生效:
每次文件被编辑或创建时,自动执行 lint 命令
text
实测 AI 会自己完成全套:读取 package.json 找到项目里配置好的 lint 命令 → 选择合适的工具(甚至会发现项目里有更快的 oxlint 而选它替代 eslint)→ 生成带文件过滤和 timeout 的脚本 → 写入 .claude/settings.json → 自己编辑一个文件来触发 Hook、验证配置生效。前提只有一个:项目里得先有可用的 lint 脚本,Hook 只是把它接到事件上。
1.4 再来一个:阻止危险的删除操作
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-dangerous.sh"
}
]
}
]
}
}
json
创建 .claude/hooks/block-dangerous.sh:
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE 'rm -rf|git push.*--force|git reset.*--hard'; then
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"危险操作已被Hook拦截"}}'
else
exit 0
fi
bash
这个Hook会在Claude执行Bash命令之前检查,如果发现 rm -rf、git push --force 等危险命令,直接拦截。
拦截要精准,不要一刀切。完全禁止删除是自找麻烦——AI 干活时清理测试脚本、过程文件、截图这类中间产物是正当需求。正确做法是像上面这样只匹配真正的危险模式(rm -rf、git push.*--force、git reset.*--hard),而不是把所有删除操作都拦下。
看到区别了吗?
| Hook | 时机 | 行为 |
|---|---|---|
PostToolUse | 工具执行之后 | 自动跑eslint,不影响流程 |
PreToolUse | 工具执行之前 | 检查命令,危险的直接拦截 |
记忆口诀:Pre 事前拦截、可以阻止;Post 事后执行、只能补充。Pre 能否决操作,Post 永远晚于操作发生,它做的是"收拾后手"(格式化、跑测试、记日志)。
二、项目级 vs 全局级:Hook配置放哪里
Hook配置可以放在两个位置,作用范围不同:
| 配置位置 | 作用范围 | 场景 |
|---|---|---|
.claude/settings.json | 当前项目,可提交到git共享给团队 | 项目特定的lint、测试、代码规范 |
~/.claude/settings.json | 所有项目全局生效 | 个人习惯(比如每次启动会话加载欢迎信息) |
.claude/settings.local.json | 当前项目,但不提交到git | 本地开发环境的特殊配置 |
怎么选?
- "这条规则只有这个项目需要" → 项目级
- "这条规则我所有项目都需要" → 全局级
- "这条规则团队其他人也应该遵守" → 项目级(提交到git)
举个例子:
# 项目级:Vue项目的eslint规则(只有Vue项目需要)
# 放在 .claude/settings.json
# 全局级:所有项目都禁止rm -rf(你的个人安全习惯)
# 放在 ~/.claude/settings.json
bash
三、常用Hook事件
不是所有事件都需要记住,日常开发最常用的就这几个:
3.1 核心事件速查
| 事件 | 什么时候触发 | 典型用途 |
|---|---|---|
SessionStart | 会话启动时 | 加载项目信息、显示欢迎语 |
UserPromptSubmit | 你提交提示词后、Claude处理前 | 自动补充上下文 |
PreToolUse | Claude调用工具之前 | 权限检查、危险操作拦截 |
PostToolUse | Claude调用工具之后 | 自动格式化、自动跑测试 |
Stop | Claude完成响应时 | 检查输出质量、提醒遗漏 |
3.2 完整事件列表(了解即可,以官方 Hooks reference 为准)
官方目前枚举了 30+ 个事件,日常用到的不到十分之一,但要知道边界在哪:
| 类别 | 事件 | 说明 |
|---|---|---|
| 会话 | SessionStart / SessionEnd | 会话开始或恢复 / 结束 |
| 会话 | Setup | --init-only / -p --maintenance 等一次性准备场景(CI 常用) |
| 用户 | UserPromptSubmit | 提示词提交后、Claude 处理前 |
| 用户 | UserPromptExpansion | 用户输入的命令展开成提示词前(可阻止展开) |
| 用户 | Stop / StopFailure | Claude 完成响应 / 本轮因 API 错误终止 |
| 工具 | PreToolUse | 工具调用前(可拦截) |
| 工具 | PermissionRequest / PermissionDenied | 权限决策时 / 自动模式拒绝调用时 |
| 工具 | PostToolUse / PostToolUseFailure | 工具调用成功后 / 失败后 |
| 工具 | PostToolBatch | 一批并行工具调用全部结束后 |
| 通知 | Notification / MessageDisplay | 系统通知 / 消息展示时 |
| Agent | SubagentStart / SubagentStop | 子Agent启动 / 结束 |
| Agent | TaskCreated / TaskCompleted | 任务清单创建 / 完成时 |
| Agent | TeammateIdle | Agent Teams 的成员即将空闲 |
| 上下文 | PreCompact / PostCompact | 上下文压缩前 / 后 |
| 配置 | InstructionsLoaded | CLAUDE.md 或 rules 文件被加载进上下文时 |
| 配置 | ConfigChange / CwdChanged / DirectoryAdded / FileChanged | 配置文件变更 / 工作目录切换 / 加目录 / 监听的文件变化 |
| 工作树 | WorktreeCreate / WorktreeRemove | worktree 创建 / 移除 |
| 模型 | PreModelSwitch / PostModelSwitch | 模型切换前(可阻止)/ 后 |
| MCP | Elicitation / ElicitationResult | MCP 服务器请求用户输入 / 用户回应后 |
完整的事件输入/输出 schema 见官方文档 Hooks reference。
四、Hook配置格式详解
4.1 完整结构
Hook配置有三层:事件 → 匹配器 → 执行器。
{
"hooks": {
"<事件名>": [ // 第一层:什么事件触发
{
"matcher": "<正则>", // 第二层:匹配什么工具/内容(可选)
"hooks": [ // 第三层:具体执行什么
{
"type": "command",
"command": "要执行的命令"
}
]
}
]
}
}
json
4.2 Matcher(匹配器)与 if 条件
Matcher 的求值规则:值里只含字母/数字/下划线/连字符/竖线时按精确匹配(Edit|Write 匹配这两个工具名),含其他字符则按 JavaScript 正则(mcp__memory__.* 匹配 memory 服务器的所有工具)。不写 matcher 或写 "*" 表示匹配所有。
// 只在Edit和Write文件时触发
"matcher": "Write|Edit"
// 只在Bash工具时触发
"matcher": "Bash"
// 只在特定MCP工具时触发(比如memory相关的MCP工具)
"matcher": "mcp__memory__.*"
// 不写matcher = 匹配所有工具(常用于SessionStart等不需要匹配的事件)
json
matcher 只能过滤工具名。要进一步按参数过滤(比如"只拦 git push"),给单个 handler 加 if 字段,它用的是权限规则语法:
{
"type": "command",
"if": "Bash(git push *)",
"command": ".claude/hooks/block-force-push.sh"
}
json
if 不匹配时脚本根本不会启动,连进程都不会拉起——比在脚本里 grep 再判断更省。
4.3 Hook 处理器类型
| 类型 | 说明 | 适用场景 |
|---|---|---|
command | 执行Shell命令 | lint、格式化、安全检查(最常用) |
http | 发送HTTP请求(POST事件JSON) | 远程校验、通知webhook、记日志 |
mcp | 调用一个MCP工具 | 复用已有MCP能力做检查 |
prompt / agent | 让Claude自己判断 | 需要理解语义的质量检查、复杂逻辑 |
另外插件还能以 JavaScript 函数的形式在 Claude Code 进程内注册 Hook(官方称 mod),那是插件开发的事,日常配置用不到。
command类型的退出码很重要:
| 退出码 | 含义 |
|---|---|
0 | 允许继续(也可以输出JSON来控制行为) |
2 | 阻止操作(用于PreToolUse拦截) |
| 其他 | 非阻塞错误,输出会显示给用户 |
五、实用场景
5.1 场景一:会话启动时显示项目状态
你每次打开Claude Code都想快速了解项目当前状态。这个Hook会在每次会话启动时自动显示。
项目级,放在 .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo \"=== 项目状态 ===\" && echo \"分支: $(git branch --show-current)\" && echo \"最近提交: $(git log -1 --pretty='%h %s')\" && echo \"未提交文件: $(git status --porcelain | wc -l | tr -d ' ')\""
}
]
}
]
}
}
json
效果:
=== 项目状态 ===
分支: feature/user-auth
最近提交: a3f21b2 feat: 添加用户登录组件
未提交文件: 3
text
5.2 场景二:编辑文件后自动格式化
前端项目最常见的需求:改完代码自动跑prettier。一行命令版(内联读 stdin):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path'); [ -f \"$FILE\" ] && npx prettier --write \"$FILE\"; exit 0"
}
]
}
]
}
}
json
逻辑复杂时建议拆成脚本文件(同 1.2 的 lint-on-edit.sh),便于 chmod 和单独调试。
5.3 场景三:提交代码前自动检查
虽然叫"提交前检查",但实际上Claude Code没有专门的 BeforeCommit 事件。做法是用 PreToolUse 匹配 Bash 工具,检查命令内容是否包含 git commit:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/pre-commit-check.sh"
}
]
}
]
}
}
json
#!/bin/bash
# .claude/hooks/pre-commit-check.sh
COMMAND=$(jq -r '.tool_input.command')
# 只在git commit时触发检查
if echo "$COMMAND" | grep -q 'git commit'; then
echo "运行提交前检查..."
npx eslint src/ --max-warnings 0
if [ $? -ne 0 ]; then
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"ESLint检查未通过,请先修复"}}'
fi
fi
bash
5.4 场景四:异步跑测试(不阻塞开发)
编辑文件后在后台跑相关测试,不影响Claude继续工作:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npx vitest run --reporter=verbose 2>&1 | tail -5",
"async": true,
"timeout": 300
}
]
}
]
}
}
json
async: true 表示后台运行,不会阻塞Claude。测试结果会在下一个回合自动注入上下文。
六、最佳实践
6.1 Hook脚本放哪
推荐在项目中建一个hooks目录,保持整洁:
项目根目录/
├── .claude/
│ ├── settings.json # Hook配置
│ ├── settings.local.json # 本地覆盖(gitignore)
│ └── hooks/ # Hook脚本
│ ├── block-dangerous.sh
│ ├── pre-commit-check.sh
│ └── auto-format.sh
text
6.2 注意事项
| 问题 | 建议 |
|---|---|
| Hook执行太慢,拖慢开发 | 快速检查放同步,耗时任务加 async: true |
| Hook输出太多,干扰上下文 | 控制输出量,只输出关键信息 |
| 脚本没有执行权限 | 记得 chmod +x .claude/hooks/*.sh |
| 全局Hook影响其他项目 | 全局只放通用规则,项目特定规则放项目级 |
| Hook报错导致流程中断 | 用 ` |
6.3 调试方法
# 手动测试Hook脚本是否正常工作
claude/hooks/block-dangerous.sh < test-input.json
# 查看Claude Code详细日志
export CLAUDE_DEBUG=1
claude
bash
七、小结
Hooks是Claude Code中唯一不需要手动触发的扩展机制。和MCP、Skills、Agent对比:
- MCP:给Claude加能力(工具),需要Claude主动调用
- Skills:给Claude加经验(知识),需要Claude主动参考
- Agent:给Claude加帮手(人力),需要你派活
- Hooks:在关键时刻自动执行你的规则,不需要任何人提醒
最实用的三个场景:
SessionStart:每次打开项目自动显示状态PostToolUse:改完代码自动格式化PreToolUse:危险操作自动拦截
配置很简单,一个JSON就能搞定。项目级和全局级各有用途,按需选择即可。
延伸阅读:
↑